// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import 'package:aws_common/aws_common.dart' hide HttpPayload;
import 'package:code_builder/code_builder.dart';
import 'package:smithy/ast.dart';
import 'package:smithy_codegen/src/generator/generator.dart';
import 'package:smithy_codegen/src/generator/serialization/protocol_traits.dart';
import 'package:smithy_codegen/src/generator/types.dart';
import 'package:smithy_codegen/src/util/shape_ext.dart';
import 'package:smithy_codegen/src/util/symbol_ext.dart';

const sdkUnknown = 'sdkUnknown';

/// Useful properties when generating named member shapes (unions + structs).
mixin NamedMembersGenerationContext<S extends NamedMembersShape, U>
    on ShapeGenerator<S, U> {
  /// All members on [shape] which are generated.
  ///
  /// Can be overriden to limit the members to be code generated.
  late final List<MemberShape> members = shape.members.values.toList();

  /// Member shapes and their [Reference] types.
  late final Map<MemberShape, Reference> memberSymbols = {
    for (final member in members)
      member: context
          .symbolFor(member.target, shape)
          .withBoxed(member.isNullable(context, shape)),
  };
}

/// Useful properties when generating union shapes.
mixin UnionGenerationContext<U> on ShapeGenerator<UnionShape, U>
    implements NamedMembersGenerationContext<UnionShape, U> {
  late final MemberShape unknownMember = MemberShape(
    (s) => s
      ..memberName = sdkUnknown
      ..shapeId = shape.shapeId.replace(member: sdkUnknown)
      ..target = const ShapeId.core('Document'),
  );
  late final Reference unknownMemberSymbol = DartTypes.core.object.unboxed;

  late final List<MemberShape> allMembers = [...members, unknownMember];

  /// Whether this represents the unknown value type.
  bool isUnknownMember(MemberShape member) => member.memberName == sdkUnknown;

  /// The name of this member as a union variant.
  String variantName(MemberShape member) =>
      member.memberName.camelCase.nameEscaped(ShapeType.union);

  /// The name of the union variant's private class name.
  String variantClassName(MemberShape member) =>
      '${'_${className}_${member.memberName}'.pascalCase}\$';
}

/// Useful properties when generating structure shapes.
mixin StructureGenerationContext<U> on ShapeGenerator<StructureShape, U>
    implements NamedMembersGenerationContext<StructureShape, U> {
  /// The symbol for the HTTP payload.
  late final Reference payloadSymbol = () {
    if (hasPayload) {
      return shape.httpPayload.symbol;
    } else if (payloadMember != null) {
      return context.symbolFor(payloadMember!.target, shape);
    }
    return symbol;
  }();

  /// The shape for the HTTP payload.
  late final Shape payloadShape = () {
    final member = payloadMember;
    if (member != null) {
      return context.shapeFor(member.target);
    }
    return shape;
  }();

  /// The symbol for the built class, to be generated by `built_value`.
  late final Reference builtSymbol = symbol.typeRef.rebuild(
    (t) => t.symbol = '_\$$className',
  );

  /// The symbol for the builder class, to be generated by `built_value`.
  late final Reference builderSymbol = symbol.typeRef.rebuild(
    (t) => t.symbol = '${className}Builder',
  );

  /// The symbol for the built payload class, to be generated by `built_value`.
  late final Reference? builtPayloadSymbol = hasBuiltPayload
      ? symbol.typeRef.rebuild((t) => t.symbol = '_\$$payloadClassName')
      : null;

  /// The symbol for the payload's builder class, to be generated by
  /// `built_value`.
  late final Reference payloadBuilderSymbol = hasBuiltPayload
      ? symbol.typeRef.rebuild((t) => t.symbol = '${payloadClassName}Builder')
      : builderSymbol;

  /// The name of the payload's class.
  late final String? payloadClassName = hasBuiltPayload
      ? '${className}Payload'
      : null;

  /// The resolved HTTP input traits.
  late final HttpInputTraits? httpInputTraits = shape.httpInputTraits();

  /// The resolved HTTP output traits.
  late final HttpOutputTraits? httpOutputTraits = shape.httpOutputTraits();

  /// The resolved HTTP error traits.
  late final HttpErrorTraits? httpErrorTraits = shape.httpErrorTraits(
    shape.httpPayload.symbol,
  );

  /// The member shape to serialize when [HttpPayloadTrait] is used.
  late final MemberShape? payloadMember = shape.httpPayload.member;

  /// The list of all members which convey some information about the request,
  /// and for most protocols are not included in the body of the request.
  late final List<MemberShape> metadataMembers = shape.metadataMembers;

  /// The list of all members which should always be included in the body of
  /// the request.
  late final List<MemberShape> payloadMembers = shape.payloadMembers;

  /// Whether the structure has an HTTP payload.
  late final bool hasPayload = shape.hasPayload(context);

  /// Whether the structure needs a payload struct.
  late final bool hasBuiltPayload = shape.hasBuiltPayload(context);

  /// Whether the structure has a streaming payload.
  late final bool hasStreamingPayload = shape.hasStreamingPayload(context);

  /// The operation this shape belongs to, if any.
  late final OperationShape? operationShape = shape.operationShape(context);
}

/// Useful properties when generating operation shapes.
mixin OperationGenerationContext<U> on ShapeGenerator<OperationShape, U> {
  late final StructureShape inputShape = shape.inputShape(context);
  late final Reference inputSymbol = shape.inputSymbol(context);
  late final HttpPayload inputPayload = inputShape.httpPayload;
  late final StructureShape outputShape = shape.outputShape(context);
  late final Reference outputSymbol = shape.outputSymbol(context);
  late final HttpPayload outputPayload = outputShape.httpPayload;

  late final List<HttpErrorTraits> errorSymbols =
      [...?context.service?.errors, ...shape.errors].whereType<ShapeRef>().map((
        error,
      ) {
        final shape = context.shapeFor(error.target) as StructureShape;
        return shape.httpErrorTraits(shape.httpPayload.symbol)!;
      }).toList();

  late final HttpTrait? httpTrait = shape.httpTrait(context);
  late final HttpInputTraits httpInputTraits = inputShape.httpInputTraits(
    overrideTrait: true,
  )!;
  late final HttpOutputTraits httpOutputTraits = outputShape.httpOutputTraits(
    overrideTrait: true,
  )!;
  late final PaginatedTraits? paginatedTraits = shape.paginatedTraits(context);
}
